
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <signal.h>
#include <sys/wait.h>

#include <AudioPlayer.h>

#ifdef __ANDROID__
#include <android/log.h>
#define BOSE_LOGI(...) __android_log_print(ANDROID_LOG_INFO,  "BOSECMD_AP", __VA_ARGS__)
#define BOSE_LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "BOSECMD_AP", __VA_ARGS__)
#define BOSE_LOGW(...) __android_log_print(ANDROID_LOG_WARN,  "BOSECMD_AP", __VA_ARGS__)
#define BOSE_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, "BOSECMD_AP", __VA_ARGS__)
#else
#define BOSE_LOGI(str, ...) do{printf("BOSECMD_AP ");printf(str,##__VA_ARGS__);printf("\n");}while(0)
#define BOSE_LOGD(str, ...) do{printf("BOSECMD_AP ");printf(str,##__VA_ARGS__);printf("\n");}while(0)
#define BOSE_LOGW(str, ...) do{printf("BOSECMD_AP ");printf(str,##__VA_ARGS__);printf("\n");}while(0)
#define BOSE_LOGE(str, ...) do{printf("BOSECMD_AP ");printf(str,##__VA_ARGS__);printf("\n");}while(0)
#endif

using namespace com::bose;

pid_t gSessionId = 0; //audio player child process PID

PlayStatus gPlayStatus = PLAY_STATUS_STOPPED;

void set_status(PlayStatus status) {
    gPlayStatus = status;
    //TODO: notify status changed
}

PlayStatus get_status() {
    return gPlayStatus;
}

PlayStatus AudioPlayer::ap_get_status() {
    return get_status();
}

void AudioPlayer::ap_set_status(PlayStatus status) {
    set_status(status);
}

AudioPlayer::~AudioPlayer() {
    BOSE_LOGD("AudioPlayer Destructor");
    if(gSessionId > 0) {
        BOSE_LOGW("still playing audio, need to be stopped first.");
        char cmd[255] = {0};
        sprintf(cmd, "kill -9 %d", gSessionId);
        system(cmd);
    }
}

static void ap_sigaction_function(int signal, siginfo_t* pInfo, void* pvo) {
    int res = pInfo->si_int;
    (void)pvo;
    switch(signal) {
        case SIGCHLD:
            pid_t pChildId;
            pChildId=waitpid(gSessionId, NULL, WNOHANG);
            if (gSessionId == pChildId) {
                BOSE_LOGE("Child %d terminated.", pChildId);
                gSessionId = 0;
                set_status(PLAY_STATUS_STOPPED);
            }
            return;
        case AP_SIGNAL_CONTROL_PAUSE:
            BOSE_LOGW("Receive AP_SIGNAL_PLAY_CONTROL. Res=%d", res);
            return;
        case AP_SIGNAL_PLAY_STATUS:
            BOSE_LOGW("Receive AP_SIGNAL_PLAY_STATUS. status=%s", status2str((PlayStatus)res));
            set_status((PlayStatus)res);
            return;
        case AP_SIGNAL_FRAME_COUNT:
            BOSE_LOGD("Receive AP_SIGNAL_FRAME_COUNT. frames=%d", res);
            return;
        case AP_SIGNAL_CURRENT_SECONDS:
            BOSE_LOGD("Receive AP_SIGNAL_REMAIN_SECONDS. Played %d seconds.", res);
            return;
        default:        //other singals
            BOSE_LOGE("Received signal 0x%X, ignore...", signal);
            return;
    }
}

AudioPlayer::AudioPlayer(const std::string url) : mUrl(url){
    BOSE_LOGD("AudioPlayer Constructor");
}

session_id AudioPlayer::play() {
    int execl_ret = -1;
    const char* p = mUrl.c_str();
    if ((gSessionId) > 0 || (PLAY_STATUS_PLAYING == get_status())) {
        // caller fix this abnormal case.
        BOSE_LOGE("AudioPlayer is still playing!");
        return AP_SESSION_ID_INVALID;
    }
    gSessionId = fork();
    if ( gSessionId < 0 ) {
        BOSE_LOGE("fork failed");
    } else if( gSessionId == 0 ) {
        BOSE_LOGD("child process, parent=%d. exec:tinyplay %s", getppid(), p);
        const char* ply = "tinyply";
        int ppid = getppid();
        execl_ret = execlp(ply, ply, p,
                           "-D", "0",
                           "-d", "0",
                           "-p", "768",
                            nullptr);

        if (execl_ret < 0) {
            BOSE_LOGE("return[%d] to execl:%s", execl_ret, p);
            union sigval value;
            value.sival_int = PLAY_STATUS_ERROR;
            sigqueue(getppid(), AP_SIGNAL_PLAY_STATUS, value);
            exit(EXIT_SUCCESS);
        } else {
            BOSE_LOGD("succeed[%d] to execl:%s", execl_ret, p);
        }
    } else {
        BOSE_LOGD("parent process, child=%d", gSessionId);
        //signal(SIGCHLD, audio_player_signal_handler);
        //signal(AP_SINGAL_PLAY_STATUS, audio_player_signal_handler);
        struct sigaction act;
        act.sa_sigaction = ap_sigaction_function;
        act.sa_flags = SA_SIGINFO;
        sigaction(AP_SIGNAL_PLAY_STATUS, &act, NULL);
        sigaction(AP_SIGNAL_FRAME_COUNT, &act, NULL);
        sigaction(AP_SIGNAL_CURRENT_SECONDS, &act, NULL);
        sigaction(SIGCHLD, &act, NULL);

        return gSessionId; // use child process id as  session id.
    }
    return 0; //no session
 }

ErrorCode AudioPlayer::stop(session_id id) {
    ErrorCode ret = AP_ERRORCODE_OK;
    char cmd[255] = {0};
    if(id != gSessionId || id < 0) {
        return AP_ERRORCODE_INVALID_ARGUMENTS;
    }

    sprintf(cmd, "kill -9 %d", id);
    if (system(cmd) < 0) {
        ret = AP_ERRORCODE_SYSTEM_ERROR;
    }
    ap_set_status(PLAY_STATUS_STOPPED);
    return ret;
}

ErrorCode AudioPlayer::pause(session_id id, bool pause) {
    ErrorCode ret = AP_ERRORCODE_OK;
    char cmd[255] = {0};
    if(id != gSessionId || id < 0) {
        BOSE_LOGE("invalid session[%d] .Current session[%d]", id, gSessionId);
        return AP_ERRORCODE_INVALID_ARGUMENTS;
    }

    union sigval value;
    value.sival_int = pause ? 1  : 0;
    sigqueue(id, AP_SIGNAL_CONTROL_PAUSE, value);
    return ret;
}

ErrorCode AudioPlayer::seek(int32_t seconds){
    ErrorCode ret = AP_ERRORCODE_OK;
    if(gSessionId  <= 0) {
        BOSE_LOGE("no session");
        return AP_ERRORCODE_INVALID_STATUS;
    }

    union sigval value;
    value.sival_int = seconds;
    sigqueue(gSessionId, AP_SIGNAL_CONTROL_SEEK, value);
    return ret;
}